/*
 * Copyright (C) 2011, Pavel Samko <bulldozerbsg@gmail.com>
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the "Pavel Samko" nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

#include "VCModWaveIn.h"

#if !VC_PLATFORM_WIN
#error ONLY FOR WINDOWS!
#endif

#pragma comment(lib, "winmm.lib")

VCModWaveInThread::VCModWaveInThread(VCModWaveIn & mod)
: VCThread()
, m_mod(mod)
, m_needstate(WORK_INIT)
, m_state(WORK_NONE)
, m_device(NULL)
, m_buff_curr(0)
{
}

int VCModWaveInThread::Execute(void * data)
{
	while (!m_mod.m_needexit)
	{
		if ((m_state != WORK_ERROR) && (m_needstate != WORK_NONE) && (m_needstate != m_state))
		{
			STATE needstate = m_needstate;
			m_needstate = WORK_NONE;
			switch(needstate)
			{
			case WORK_INIT:
				if ((m_state == WORK_NONE) && !m_device)
				{
					WAVEFORMATEX format;
					format.wFormatTag = WAVE_FORMAT_PCM;
					format.nChannels = 1;
					format.nSamplesPerSec = 32000;
					format.nAvgBytesPerSec = 32000 * 2;
					format.nBlockAlign = 2;
					format.wBitsPerSample = 16;
					format.cbSize = 0;
					MMRESULT r = waveInOpen(&m_device, WAVE_MAPPER, &format, 0, 0, WAVE_FORMAT_DIRECT);
					if (r == MMSYSERR_NOERROR)
					{
						m_buff_1_hdr.lpData = (LPSTR)m_buff_1;
						m_buff_1_hdr.dwBufferLength = VCMODWAVEIN_BUFF_SIZE * sizeof(short int);
						m_buff_1_hdr.dwBytesRecorded = 0;
						m_buff_1_hdr.dwUser = 0;
						m_buff_1_hdr.dwFlags = 0;
						m_buff_1_hdr.dwLoops = 0;

						m_buff_2_hdr.lpData = (LPSTR)m_buff_2;
						m_buff_2_hdr.dwBufferLength = VCMODWAVEIN_BUFF_SIZE * sizeof(short int);
						m_buff_2_hdr.dwBytesRecorded = 0;
						m_buff_2_hdr.dwUser = 0;
						m_buff_2_hdr.dwFlags = 0;
						m_buff_2_hdr.dwLoops = 0;

						m_buff_curr = 1;

						m_mod.m_controller.OnNotifyFromMod(m_mod, VC_INFO_INIT);
						m_state = WORK_STOP;
						TRACE("wave in init ok")
					} else
					{
						m_mod.m_controller.OnNotifyFromMod(m_mod, VC_ERROR_INIT);
						m_state = WORK_ERROR;
						TRACE("wave in init error")
					}
				}
				break;
			case WORK_START:
				if (m_state == WORK_STOP)
				{
					if (!m_device)
					{
						m_mod.m_controller.OnNotifyFromMod(m_mod, VC_ERROR);
						m_state = WORK_ERROR;
						break;
					}
					if (m_mod.m_reciver)
					{
						m_mod.m_reciver->Push(VC_STREAM_BEGIN, NULL, 0, VC_PCM_1_32000_S16);
					}
					// add 1 buffer
					waveInPrepareHeader(m_device, &m_buff_1_hdr, sizeof(WAVEHDR));
					waveInAddBuffer(m_device, &m_buff_1_hdr, sizeof(WAVEHDR));
					// 1 buffer current
					m_buff_curr = 1;
					// add 2 buffer
					waveInPrepareHeader(m_device, &m_buff_2_hdr, sizeof(WAVEHDR));
					waveInAddBuffer(m_device, &m_buff_2_hdr, sizeof(WAVEHDR));

					waveInStart(m_device);
					m_mod.m_controller.OnNotifyFromMod(m_mod, VC_INFO_STREAM_BEGIN);
					m_state = WORK_START;
				}
				break;
			case WORK_STOP:
				if (m_state == WORK_START)
				{
					if (!m_device)
					{
						m_mod.m_controller.OnNotifyFromMod(m_mod, VC_ERROR);
						m_state = WORK_ERROR;
						break;
					}
					waveInStop(m_device);
					waveInUnprepareHeader(m_device, &m_buff_1_hdr, sizeof(WAVEHDR));
					waveInUnprepareHeader(m_device, &m_buff_2_hdr, sizeof(WAVEHDR));
					switch (m_buff_curr)
					{
					case 1:
						if (m_mod.m_reciver)
						{
							m_mod.m_reciver->Push(VC_STREAM_END, m_buff_1, m_buff_1_hdr.dwBytesRecorded, VC_PCM_1_32000_S16);
						}
						break;
					case 2:
						if (m_mod.m_reciver)
						{
							m_mod.m_reciver->Push(VC_STREAM_END, m_buff_2, m_buff_2_hdr.dwBytesRecorded, VC_PCM_1_32000_S16);
						}
						break;
					}
					m_mod.m_controller.OnNotifyFromMod(m_mod, VC_INFO_STREAM_END);
					m_state = WORK_STOP;
				}
				break;
			case WORK_ABORT:
				if (m_state == WORK_START)
				{
					if (!m_device)
					{
						m_mod.m_controller.OnNotifyFromMod(m_mod, VC_ERROR);
						m_state = WORK_ERROR;
						break;
					}
					waveInStop(m_device);
					waveInUnprepareHeader(m_device, &m_buff_1_hdr, sizeof(WAVEHDR));
					waveInUnprepareHeader(m_device, &m_buff_2_hdr, sizeof(WAVEHDR));
					if (m_mod.m_reciver)
					{
						m_mod.m_reciver->Push(VC_STREAM_END, NULL, 0, VC_PCM_1_32000_S16);
					}
					m_mod.m_controller.OnNotifyFromMod(m_mod, VC_INFO_STREAM_END);
					m_state = WORK_STOP;
				}
				break;
			}
		} else
		{
			if ((m_state == WORK_ERROR) && (m_needstate != WORK_NONE))
			{
				m_mod.m_controller.OnNotifyFromMod(m_mod, VC_ERROR);
				m_needstate = WORK_NONE;
			} else
			{
				switch(m_state)
				{
				case WORK_START:
					switch (m_buff_curr)
					{
					case 1:
						if (waveInUnprepareHeader(m_device, &m_buff_1_hdr, sizeof(WAVEHDR)) != WAVERR_STILLPLAYING)
						{
							if (m_mod.m_reciver)
							{
								m_mod.m_reciver->Push(VC_STREAM, m_buff_1, m_buff_1_hdr.dwBytesRecorded, VC_PCM_1_32000_S16);
							}
							waveInPrepareHeader(m_device, &m_buff_1_hdr, sizeof(WAVEHDR));
							waveInAddBuffer(m_device, &m_buff_1_hdr, sizeof(WAVEHDR));
							m_buff_curr = 2;
						}
						break;
					case 2:
						if (waveInUnprepareHeader(m_device, &m_buff_2_hdr, sizeof(WAVEHDR)) != WAVERR_STILLPLAYING)
						{
							if (m_mod.m_reciver)
							{
								m_mod.m_reciver->Push(VC_STREAM, m_buff_2, m_buff_2_hdr.dwBytesRecorded, VC_PCM_1_32000_S16);
							}
							waveInPrepareHeader(m_device, &m_buff_2_hdr, sizeof(WAVEHDR));
							waveInAddBuffer(m_device, &m_buff_2_hdr, sizeof(WAVEHDR));
							m_buff_curr = 1;
						}
						break;
					}
					break;
				}
			}
		}
		VCSleep(1);
	}
	return 0;
}

void VCModWaveInThread::OnExit(int exitcode)
{
	if (m_device)
	{
		if (m_state == WORK_START) // abnormal situation
		{
			waveInStop(m_device);
			waveInUnprepareHeader(m_device, &m_buff_1_hdr, sizeof(WAVEHDR));
			waveInUnprepareHeader(m_device, &m_buff_2_hdr, sizeof(WAVEHDR));
			if (m_mod.m_reciver)
			{
				m_mod.m_reciver->Push(VC_STREAM_END, NULL, 0, VC_PCM_1_32000_S16);
			}
			m_state = WORK_STOP;
		}

		waveInClose(m_device);
		m_device = NULL;
	}
}

VC_RESULT VCModWaveInThread::SetNeedState(STATE needstate)
{
	VC_RESULT result = VC_FALSE;
	if (m_needstate != WORK_INIT)
	{
		m_needstate = needstate;
		result = VC_OK;
	}
	return result;
}

VCModWaveIn::VCModWaveIn(VCIController & controller)
: VCIMod(controller)
, m_needexit(false)
{
	m_thread = new VCModWaveInThread(*this);
}

VCModWaveIn::~VCModWaveIn()
{
	m_needexit = true;
	if (m_thread)
	{
		m_thread->Wait();
		delete m_thread;
	}
}
	
VC_RESULT VCModWaveIn::Push(VC_CMD cmd, void * data_buff, size_t data_size, VC_DATAFORMAT data_format)
{
	VC_RESULT result = VC_FALSE;
	switch(cmd)
	{
	case VC_START:
		if (m_thread)
		{
			result = m_thread->SetNeedState(VCModWaveInThread::WORK_START);
		}
		break;
	case VC_STOP:
		if (m_thread)
		{
			result = m_thread->SetNeedState(VCModWaveInThread::WORK_STOP);
		}
		break;
	case VC_ABORT:
		if (m_thread)
		{
			result = m_thread->SetNeedState(VCModWaveInThread::WORK_ABORT);
		}
		break;
	default:
		if (m_reciver)
		{
			result = m_reciver->Push(cmd, data_buff, data_size, data_format);
		}
	}
	return result;
}

